﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
#if TASKSUPPORT
using System.Threading.Tasks;
#endif
using MugenMvvmToolkit.Annotations;
using MugenMvvmToolkit.Attributes;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;

namespace MugenMvvmToolkit.ViewModels
{
    /// <summary>
    ///     Represent the class for editable view models.
    /// </summary>
    [BaseViewModel(Priority = 5)]
    public class EditableViewModel<T> : ValidatableViewModel, IEditableViewModel<T> where T : class
    {
        #region Fields

        private T _entity;
        private bool _hasChanges;

        /// <summary>
        ///     Gets the value that indicates that entity initialized.
        /// </summary>
        private bool _isEditorInitialized;

        private bool _isNewRecord;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="EditableViewModel{T}" /> class.
        /// </summary>
        public EditableViewModel()
        {
            var entityFactory = GlobalSettings.DefaultEntityFactory;
            if (entityFactory != null)
                Entity = (T)entityFactory(typeof(T));
            UpdateChangesOnEntityPropertyChanged = true;
        }

        #endregion

        #region Implementation of IEditableViewModel

        /// <summary>
        ///     Gets the type of model.
        /// </summary>
        public Type ModelType
        {
            get { return typeof(T); }
        }

        /// <summary>
        ///     Gets the value which indicates that is the new entity or not.
        /// </summary>
        public bool IsNewRecord
        {
            get { return _isNewRecord; }
            protected set
            {
                if (value.Equals(_isNewRecord)) return;
                _isNewRecord = value;
                OnPropertyChanged("IsNewRecord");
            }
        }

        /// <summary>
        ///     Gets a value indicating whether the entity has changes.
        /// </summary>
        public virtual bool HasChanges
        {
            get
            {
                if (!IsInitialized)
                    return false;
                if (StateProvider != null && StateProvider.IsSupportChangeDetection)
                    return _hasChanges || StateProvider.HasChanges(Entity);
                return _hasChanges;
            }
            protected internal set
            {
                if (value.Equals(_hasChanges)) return;
                _hasChanges = value;
                OnPropertyChanged("HasChanges");
            }
        }

        /// <summary>
        ///     Gets a value indicating whether the entity is initialized.
        /// </summary>
        public virtual bool IsEntityInitialized
        {
            get { return _isEditorInitialized; }
        }

        /// <summary>
        ///     Gets the edited entity.
        /// </summary>
        object IEditableViewModel.Entity
        {
            get { return Entity; }
        }

        /// <summary>
        ///     Initializes the specified entity to edit.
        /// </summary>
        /// <param name="entity">The specified entity to edit.</param>
        /// <param name="isNewRecord">
        ///     If <c>true</c> is new entity;otherwise <c>false</c>.
        /// </param>
        void IEditableViewModel.InitializeEntity(object entity, bool isNewRecord)
        {
            InitializeEntity((T)entity, isNewRecord);
        }

        /// <summary>
        ///     Applies the changes of entity.
        /// </summary>
        /// <returns>A series of instances of <see cref="IEntityStateEntry" />.</returns>
        public IList<IEntityStateEntry> ApplyChanges()
        {
            EnsureIsNotDisposed();
            if (!IsEntityInitialized)
                Thrower.EditorIsNotInitialized();
            T entity;
            IList<IEntityStateEntry> result = ApplyChangesInternal(out entity);
            Entity = entity;
            Should.PropertyBeNotNull(Entity, "Entity");
            OnChangesApplied(result);
            OnChangesAppliedEvent(result);
            OnEndEdit();
            return result;
        }

        /// <summary>
        ///     Cancels the changes of entity.
        /// </summary>
        /// <returns>An instance of object.</returns>
        object IEditableViewModel.CancelChanges()
        {
            return CancelChanges();
        }

        /// <summary>
        ///     Occurs at the end of initialization the entity.
        /// </summary>
        event EntityHandler<object> IEditableViewModel.EntityInitialized
        {
            add { EntityInitialized += value; }
            remove { EntityInitialized -= value; }
        }

        /// <summary>
        ///     Occurs at the end of apply entity changes.
        /// </summary>
        public event EntityHandler<IList<IEntityStateEntry>> ChangesApplied;

        /// <summary>
        ///     Occurs at the end of cancel entity changes.
        /// </summary>
        event EntityHandler<object> IEditableViewModel.ChangesCanceled
        {
            add { ChangesCanceled += value; }
            remove { ChangesCanceled -= value; }
        }

        /// <summary>
        ///     Gets the edited entity.
        /// </summary>
        public T Entity
        {
            get { return _entity; }
            protected set
            {
                if (Equals(value, _entity)) return;
                _entity = value;
                OnPropertyChanged("Entity");
            }
        }

        /// <summary>
        ///     Initializes the specified entity to edit.
        /// </summary>
        /// <param name="entity">The specified entity to edit.</param>
        /// <param name="isNewRecord">
        ///     If <c>true</c> is new entity;otherwise <c>false</c>.
        /// </param>
        public void InitializeEntity(T entity, bool isNewRecord)
        {
            EnsureIsNotDisposed();
            Should.NotBeNull(entity, "entity");
            InvokeAfterInitialization(() =>
                                      {
                                          IsNewRecord = isNewRecord;
                                          StateProvider = GetEntityStateProvider();
                                          T saved = SaveState(entity);
                                          Entity = saved;
                                          Should.PropertyBeNotNull(Entity, "Entity");
                                          OnEntityInitialized();
                                          OnEntityInitializedEvent(saved);
                                          OnBeginEdit();
                                      });
        }

        /// <summary>
        ///     Cancels the changes of entity.
        /// </summary>
        /// <returns>An instance of object.</returns>
        public T CancelChanges()
        {
            EnsureIsNotDisposed();
            if (!IsEntityInitialized)
                Thrower.EditorIsNotInitialized();
            T cancel = CancelChangesInternal();
            Entity = cancel;
            OnChangesCanceled();
            OnChangesCanceledEvent(cancel);
            OnEndEdit();
            return cancel;
        }

        /// <summary>
        ///     Occurs at the end of initialization the entity.
        /// </summary>
        public event EntityHandler<T> EntityInitialized;

        /// <summary>
        ///     Occurs at the end of cancel entity changes.
        /// </summary>
        public event EntityHandler<T> ChangesCanceled;

        #endregion

        #region Properties

        /// <summary>
        ///     Gets or sets a value indicating whether the editor should automatically be updated changes on a entity property
        ///     change.
        /// </summary>
        public bool UpdateChangesOnEntityPropertyChanged { get; set; }

        /// <summary>
        ///     Gets the entity state provider.
        /// </summary>
        public IEntityStateProvider StateProvider { get; protected set; }

        #endregion

        #region Methods

#if TASKSUPPORT
        /// <summary>
        ///     Updates information about errors in the specified property.
        /// </summary>
        [SuppressTaskBusyHandler]
        protected Task Validate(Expression<Func<T, object>> getProperty)
        {
            Should.NotBeNull(getProperty, "getProperty");
            return Validate(MvvmUtilsInternal.ParsePropertyExpression(getProperty));
        }
#else
        /// <summary>
        ///     Updates information about errors in the specified property.
        /// </summary>
        protected IOperationResult Validate(Expression<Func<T, object>> getProperty)
        {
            Should.NotBeNull(getProperty, "getProperty");
            return Validate(MvvmUtilsInternal.ParsePropertyExpression(getProperty));
        }
#endif


        /// <summary>
        ///     Gets an instance of <see cref="IEntityStateProvider" />.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IEntityStateProvider" />.
        /// </returns>
        protected virtual IEntityStateProvider GetEntityStateProvider()
        {
            if (StateProvider != null)
                return StateProvider;
            return Get<IEntityStateProvider>();
        }

        /// <summary>
        ///     Saves a state of entity.
        /// </summary>
        /// <param name="entity">The specified entity.</param>
        /// <returns>An instance of object.</returns>
        protected virtual T SaveState(T entity)
        {
            return (T)StateProvider.Save(entity);
        }

        /// <summary>
        ///     Occurs after an entity instance is initialized.
        /// </summary>
        protected virtual void OnEntityInitialized()
        {
        }

        /// <summary>
        ///     Applies the changes of entity.
        /// </summary>
        /// <returns>A series of instances of <see cref="IEntityStateEntry" />.</returns>
        protected virtual IList<IEntityStateEntry> ApplyChangesInternal(out T entity)
        {
            entity = (T)StateProvider.Apply(Entity);
            return GetChanges(entity);
        }

        /// <summary>
        ///     Gets the changes.
        /// </summary>
        /// <param name="entity">The saved entity.</param>
        /// <returns>A series of instances of <see cref="IEntityStateEntry" />.</returns>
        protected virtual IList<IEntityStateEntry> GetChanges(T entity)
        {
            return new List<IEntityStateEntry>
                   {
                       EntityStateEntry.Create(IsNewRecord ? EntityState.Added : EntityState.Modified, entity)
                   };
        }

        /// <summary>
        ///     Occurs after applying changes.
        /// </summary>
        /// <param name="entityStateEntries">The entity state entries.</param>
        protected virtual void OnChangesApplied(IList<IEntityStateEntry> entityStateEntries)
        {
        }

        /// <summary>
        ///     Cancels the changes of entity.
        /// </summary>
        /// <returns>An instance of object.</returns>
        protected virtual T CancelChangesInternal()
        {
            return (T)StateProvider.Cancel(Entity);
        }

        /// <summary>
        ///     Occurs after canceling changes.
        /// </summary>
        protected virtual void OnChangesCanceled()
        {
        }

        /// <summary>
        ///     Raises the <c>EntityInitialized</c> event
        /// </summary>
        protected void OnEntityInitializedEvent(T entity)
        {
            EntityHandler<T> handler = EntityInitialized;
            if (handler != null)
                handler(this, entity);
        }

        /// <summary>
        ///     Raises the <c>ChangesApplied</c> event
        /// </summary>
        protected void OnChangesAppliedEvent(IList<IEntityStateEntry> entityStateEntries)
        {
            EntityHandler<IList<IEntityStateEntry>> handler = ChangesApplied;
            if (handler != null)
                handler(this, entityStateEntries);
        }

        /// <summary>
        ///     Raises the <c>ChangesCanceled</c> event
        /// </summary>
        protected void OnChangesCanceledEvent(T entity)
        {
            EntityHandler<T> handler = ChangesCanceled;
            if (handler != null) handler(this, entity);
        }

        internal virtual void OnBeginEdit()
        {
            Should.PropertyBeNotNull(Entity, "Entity");
#if NETFX_CORE
            OnPropertyChanged(string.Empty);
#else
            OnPropertyChanged(null);
#endif
            AddInstance(Entity);
            ValidateAll().WithTaskExceptionHandler(this);
            _isEditorInitialized = true;
            HasChanges = IsNewRecord;
            var notifyPropertyChanged = Entity as INotifyPropertyChanged;
            if (notifyPropertyChanged != null)
                notifyPropertyChanged.PropertyChanged += NotifyPropertyChangedOnPropertyChanged;
        }

        private void OnEndEdit()
        {
            if (Entity != null)
                RemoveInstance(Entity);
            ClearErrors();
            var notifyPropertyChanged = Entity as INotifyPropertyChanged;
            if (notifyPropertyChanged != null)
                notifyPropertyChanged.PropertyChanged -= NotifyPropertyChangedOnPropertyChanged;
        }

        private void NotifyPropertyChangedOnPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (!IsEntityInitialized || !UpdateChangesOnEntityPropertyChanged ||
                IgnoreProperties.Contains(args.PropertyName))
                return;
            OnPropertyChanged("HasChanges");
        }

        #endregion

        #region Overrides of ValidatableViewModel

        /// <summary>
        ///     Occurs after current view model disposed, use for clear resource and event listeners(Internal only).
        /// </summary>
        internal override void OnDisposeInternal(bool disposing)
        {
            if (disposing)
            {
                EntityHandler<T> entityInitialized = EntityInitialized;
                if (entityInitialized != null)
                {
                    foreach (EntityHandler<T> @delegate in entityInitialized.GetInvocationList())
                        EntityInitialized -= @delegate;
                }

                EntityHandler<IList<IEntityStateEntry>> changesApplied = ChangesApplied;
                if (changesApplied != null)
                {
                    foreach (EntityHandler<IList<IEntityStateEntry>> @delegate in changesApplied.GetInvocationList())
                        ChangesApplied -= @delegate;
                }

                EntityHandler<T> changesCanceled = ChangesCanceled;
                if (changesCanceled != null)
                {
                    foreach (EntityHandler<T> @delegate in changesCanceled.GetInvocationList())
                        ChangesCanceled -= @delegate;
                }
            }
            base.OnDisposeInternal(disposing);
        }

        #endregion
    }
}